/*
 * Copyright 2009 XueSong Guo.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package cn.webwheel.utils;

import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.*;

/**
 * 文件下载结果。
 */
public class DownloadResult extends SimpleResult {

    private InputStream stream;
    private File file;
    private String contentType;
    private boolean rangeSupport;
    private String fileName;

    /**
     * 直接指定文件提供下载。
     * @param file 服务器端文件
     */
    public DownloadResult(File file) {
        this(file, null);
    }

    /**
     * 直接指定文件和内容类型提供下载。
     * @param file 服务器端文件
     * @param contentType mime内容类型，如"audio/x-mpeg"
     */
    public DownloadResult(File file, String contentType) {
        this(file, contentType, true, null);
    }

    /**
     * 直接指定文件提供下载。
     * @param file 服务器端文件
     * @param contentType mime内容类型
     * @param rangeSupport 是否支持断点续传
     * @param fileName 传递到客户端的文件名称
     */
    public DownloadResult(File file, String contentType, boolean rangeSupport, String fileName) {
        this.file = file;
        this.contentType = contentType;
        this.rangeSupport = rangeSupport;
        this.fileName = fileName;
    }

    /**
     * 通过输入流提供文件下载，使用"application/octet-stream"内容类型。
     * @param stream 输入流
     */
    public DownloadResult(InputStream stream) {
        this(stream, "application/octet-stream");
    }

    /**
     * 通过输入流提供文件下载。
     * @param stream 输入流
     * @param contentType mime内容类型
     */
    public DownloadResult(InputStream stream, String contentType) {
        this(stream, contentType, null);
    }

    /**
     * 通过输入流提供文件下载。
     * @param stream 输入流
     * @param contentType mime内容类型
     * @param fileName 传递到客户端的文件名称
     */
    public DownloadResult(InputStream stream, String contentType, String fileName) {
        this.stream = stream;
        this.contentType = contentType;
        this.fileName = fileName;
    }

    /**
     * 执行文件下载
     * @throws Exception 任何异常
     */
    public void render() throws Exception {

        HttpServletRequest request = context.getRequest();
        HttpServletResponse response = context.getResponse();
        if (contentType != null) {
            response.setContentType(contentType);
        }
        if (fileName != null) {
            response.setHeader("Content-Disposition", "attachment;filename=\"" + fileName + "\"");
        }

        if (stream != null) {
            output(stream, response);
            return;
        }

        if (!file.exists() || !file.isFile()) {
            response.sendError(HttpServletResponse.SC_NOT_FOUND);
            return;
        }

        String range = request.getHeader("Range");
        if (range == null || !rangeSupport) {
            if (rangeSupport) {
                response.setHeader("Accept-Ranges", "bytes");
            }
            response.setContentLength((int) file.length());
            output(new FileInputStream(file), response);
            return;
        }

        int first, last;

        try {
            range = range.substring(6);
            int i = range.indexOf('-');
            if (i == 0) {
                last = (int) file.length() - 1;
                first = last - Integer.parseInt(range.substring(1));
                if (first < 0) first = 0;
            } else if (i == range.length() - 1) {
                first = Integer.parseInt(range.substring(0, range.length() - 1));
                last = (int) file.length() - 1;
            } else {
                first = Integer.parseInt(range.substring(0, i));
                last = Integer.parseInt(range.substring(i + 1));
            }
            if (last >= file.length()) last = (int) file.length() - 1;
            if (first < 0 || first > last) throw new Exception();
        } catch (Exception e) {
            response.sendError(HttpServletResponse.SC_REQUESTED_RANGE_NOT_SATISFIABLE);
            return;
        }

        response.setContentLength(last - first + 1);
        response.setHeader("Content-Range", "bytes " + first + "-" + last + "/" + file.length());
        response.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT);

        ServletOutputStream os = response.getOutputStream();
        RandomAccessFile raf = new RandomAccessFile(file, "r");
        byte[] buf = new byte[8192];
        try {
            raf.seek(first);
            do {
                int rd = raf.read(buf, 0, Math.min(buf.length, last - (int) raf.getFilePointer() + 1));
                os.write(buf, 0, rd);
            } while (raf.getFilePointer() <= last);
        } finally {
            raf.close();
        }
    }

    private void output(InputStream stream, HttpServletResponse response) throws IOException {
        try {
            ServletOutputStream os = response.getOutputStream();
            byte[] buf = new byte[8192];
            int rd;
            while ((rd = stream.read(buf)) != -1) {
                os.write(buf, 0, rd);
            }
        } finally {
            stream.close();
        }
    }
}
